/* Copyright (C) 2014 Daniel Dressler and contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. */

#define _GNU_SOURCE

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include <limits.h>

#include "http.h"
#include "logging.h"

#define BUFFER_STEP (1 << 12)

struct http_message_t *http_message_new() {
    struct http_message_t *msg = calloc(1, sizeof(*msg));
    if (msg == NULL) {
        ERR("failed to alloc space for http message");
        return NULL;
    }

    msg->spare_capacity = 0;
    msg->spare_filled = 0;
    msg->spare_buffer = NULL;

    return msg;
}

void message_free(struct http_message_t *msg) {
    free(msg->spare_buffer);
    free(msg);
}

static void packet_check_completion(struct http_packet_t *pkt) {
    struct http_message_t *msg = pkt->parent_message;
    /* Msg full */
    if (msg->claimed_size && msg->received_size >= msg->claimed_size) {
        msg->is_completed = 1;
        NOTE("http: Message completed: Received size >= claimed size");

        /* Sanity check */
        if (msg->spare_filled > 0)
            ERR_AND_EXIT("Msg spare not empty upon completion");
    }

    /* Pkt full */
    if (pkt->expected_size && pkt->filled_size >= pkt->expected_size) {
        pkt->is_completed = 1;
        NOTE("http: Packet completed: Packet full");
    }

    /* Pkt over capacity */
    if (pkt->filled_size > pkt->buffer_capacity) {
        /* Santiy check */
        ERR_AND_EXIT("Overflowed packet buffer");
    }
}

static int doesMatch(const char *matcher, size_t matcher_len,
                     const uint8_t *key, size_t key_len) {
    for (size_t i = 0; i < matcher_len; i++)
        if (i >= key_len || matcher[i] != key[i])
            return 0;
    return 1;
}

static int inspect_header_field(struct http_packet_t *pkt, size_t header_size,
                                char *key, size_t key_size) {
    /* Find key */
    uint8_t *pos = memmem(pkt->buffer, header_size, key, key_size);
    if (pos == NULL)
        return -1;

    /* Find first digit */
    size_t number_pos = (size_t) (pos - pkt->buffer) + key_size;
    while (number_pos < pkt->filled_size && !isdigit(pkt->buffer[number_pos]))
        ++number_pos;

    /* Find next non-digit */
    size_t number_end = number_pos;
    while (number_end < pkt->filled_size && isdigit(pkt->buffer[number_end]))
        ++number_end;

    /* Failed to find next non-digit
       header field might be broken */
    if (number_end >= pkt->filled_size)
        return -1;

    /* Temporary stringification of buffer for atoi() */
    uint8_t original_char = pkt->buffer[number_end];
    pkt->buffer[number_end] = '\0';
    int val = atoi((const char *) (pkt->buffer + number_pos));

    /* Restore buffer */
    pkt->buffer[number_end] = original_char;
    return val;
}

static void packet_store_excess(struct http_packet_t *pkt) {
    struct http_message_t *msg = pkt->parent_message;
    if (msg->spare_buffer != NULL)
        ERR_AND_EXIT("Do not store excess to non-empty packet");

    if (pkt->expected_size >= pkt->filled_size)
        ERR_AND_EXIT("Do not call packet_store_excess() unless needed");

    size_t spare_size = pkt->filled_size - pkt->expected_size;
    size_t non_spare = pkt->expected_size;
    NOTE("HTTP: Storing %d bytes of excess", spare_size);

    /* Align to BUFFER_STEP */
    size_t needed_size = 0;
    needed_size += spare_size / BUFFER_STEP;
    needed_size += (spare_size % BUFFER_STEP) > 0 ? BUFFER_STEP : 0;

    if (msg->spare_buffer == NULL) {
        uint8_t *buffer = calloc(1, needed_size);
        if (buffer == NULL)
            ERR_AND_EXIT("Failed to alloc msg spare buffer");
        msg->spare_buffer = buffer;
    }

    memcpy(msg->spare_buffer, pkt->buffer + non_spare, spare_size);
    pkt->filled_size = non_spare;

    msg->spare_capacity = needed_size;
    msg->spare_filled = spare_size;
}

static void packet_take_spare(struct http_packet_t *pkt) {
    struct http_message_t *msg = pkt->parent_message;
    if (msg->spare_filled == 0)
        return;

    if (msg->spare_buffer == NULL)
        return;

    if (pkt->filled_size > 0)
        ERR_AND_EXIT("pkt should be empty when loading msg spare");

    /* Take message's buffer */
    size_t msg_size = msg->spare_capacity;
    size_t msg_filled = msg->spare_filled;
    uint8_t *msg_buffer = msg->spare_buffer;

    pkt->buffer_capacity = msg_size;
    pkt->filled_size = msg_filled;
    pkt->buffer = msg_buffer;

    msg->spare_capacity = 0;
    msg->spare_filled = 0;
    msg->spare_buffer = NULL;
}

static ssize_t packet_find_chunked_size(struct http_packet_t *pkt) {
    /* NOTE:
       chunks can have trailers which are
       tacked on http header fields.
       NOTE:
       chunks may also have extensions.
       No one uses or supports them. */

    /* Find end of size string */
    if (pkt->filled_size >= SSIZE_MAX)
        ERR_AND_EXIT("Buffer beyond sane size");

    ssize_t max = (ssize_t) pkt->filled_size;
    ssize_t size_end = -1;
    ssize_t miniheader_end = -1;
    ssize_t delimiter_start = -1;
    for (ssize_t i = 0; i < max; i++) {

        uint8_t *buf = pkt->buffer;
        if (size_end < 0) {
            /* No extension */
            if (i + 1 < max &&
                (buf[i] == '\r' &&      /* CR */
                 buf[i + 1] == '\n')) { /* LF */
                size_end = i + 1;
                miniheader_end = size_end;
                delimiter_start = i;
                break;
            }

            /* No extension */
            if (buf[i] == '\n') { /* LF */
                size_end = i;
                miniheader_end = size_end;
                delimiter_start = i;
                break;
            }

            /* Has extensions */
            if (buf[i] == ';') {
                size_end = i;
                continue;
            }
        }
        if (miniheader_end < 0) {
            if (i + 1 < max &&
                (buf[i] == '\r' &&      /* CR */
                 buf[i + 1] == '\n')) { /* LF */
                miniheader_end = i + 1;
                delimiter_start = i;
                break;
            }

            if (buf[i] == '\n') { /* LF */
                miniheader_end = i;
                delimiter_start = i;
                break;
            }
        }
    }

    if (miniheader_end < 0) {
        /* NOTE: knowing just the size field
           is not enough since the extensions
           are not included in the size */
        NOTE("failed to find chunk mini-header so far");
        return -1;
    }

    /* Temporary stringification for strtol() */
    uint8_t original_char = *(pkt->buffer + size_end);
    *(pkt->buffer + size_end) = '\0';
    size_t size = strtoul((char *) pkt->buffer, NULL, 16);
    NOTE("Chunk size raw: %s", pkt->buffer);
    *(pkt->buffer + size_end) = original_char;
    if (size > SSIZE_MAX)
        ERR_AND_EXIT("chunk size is insane");

    if (size > 0) {
        /* Regular chunk */
        ssize_t chunk_size = (ssize_t) size; /* Chunk body */
        chunk_size += miniheader_end + 1; /* Mini-header */
        chunk_size += 2; /* Trailing CRLF */
        NOTE("HTTP: Chunk size: %lu", chunk_size);
        return (ssize_t) chunk_size;
    }

    /* Terminator chunk
       May have trailers in body */
    ssize_t full_size = -1;
    for (ssize_t i = delimiter_start; i < max; i++) {
        uint8_t *buf = pkt->buffer;
        if (i + 3 < max &&
            (buf[i] == '\r' &&      /* CR */
             buf[i + 1] == '\n' &&  /* LF */
             buf[i + 2] == '\r' &&  /* CR */
             buf[i + 3] == '\n')) { /* LF */
            full_size = i + 4;
            break;
        }

        if (i + 1 < max &&
            buf[i] == '\n' &&     /* LF */
            buf[i + 1] == '\n') { /* LF */
            full_size = i + 2;
            break;
        }
    }

    if (full_size < 0) {
        NOTE("Chunk miniheader present but body incomplete");
        return -1;
    }

    NOTE("Found end chunked packet");
    pkt->parent_message->is_completed = 1;
    pkt->is_completed = 1;
    return full_size;
}

static ssize_t packet_get_header_size(struct http_packet_t *pkt) {
    if (pkt->header_size != 0)
        goto found;

    /*
     * RFC2616 recomends we match newline on \n despite full
     * complience requires the message to use only \r\n
     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3
     */

    /* Find header */
    for (size_t i = 0; i < pkt->filled_size && i < SSIZE_MAX; i++) {
        /* two \r\n pairs */
        if ((i + 3) < pkt->filled_size &&
            '\r' == pkt->buffer[i] &&
            '\n' == pkt->buffer[i + 1] &&
            '\r' == pkt->buffer[i + 2] &&
            '\n' == pkt->buffer[i + 3]) {
            pkt->header_size = i + 4;
            goto found;
        }

        /* two \n pairs */
        if ((i + 1) < pkt->filled_size &&
            '\n' == pkt->buffer[i] &&
            '\n' == pkt->buffer[i + 1]) {
            pkt->header_size = i + 2;
            goto found;
        }
    }

    return -1;

    found:
    return (ssize_t) pkt->header_size;
}

enum http_request_t packet_find_type(struct http_packet_t *pkt) {
    enum http_request_t type = HTTP_UNSET;
    size_t size = 0;
    /*
     * Valid methods for determining http request
     * size are defined by W3 in RFC2616 section 4.4
     * link: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
     */

    /*
     * This function attempts to find what method this
     * packet would use. This is only possible in specific case:
     * 1. if the request uses method 1 we can check the http
     *    request type. We must be called on a packet which
     *    has the full header.
     * 2. if the request uses method 2 we need the full header
     *    but a simple network-byte-order-aware string search
     *    works. This function does not work if called with
     *    a chunked transport's sub-packet.
     * 3. if the request uses method 3 we again perform the
     *    string search.
     *
     * All cases require the packat to contain the full header.
     */

    ssize_t header_size_raw = packet_get_header_size(pkt);
    if (header_size_raw < 0) {
        /* We don't have the header yet */
        goto do_ret;
    }
    size_t header_size = (size_t) header_size_raw;

    /* Try Transfer-Encoding Chunked */
    char xfer_encode_str[] = "Transfer-Encoding: chunked";
    size_t xfer_encode_str_size = sizeof(xfer_encode_str) - 1;
    uint8_t *xfer_encode_pos = memmem(pkt->buffer, header_size,
                                      xfer_encode_str,
                                      xfer_encode_str_size);
    if (xfer_encode_pos != NULL) {
        size = 0;
        type = HTTP_CHUNKED;
        goto do_ret;
    }

    /* Try Content-Length */
    char content_length_str[] = "Content-Length: ";
    ssize_t contlen_size =
            inspect_header_field(pkt, header_size,
                                 content_length_str, sizeof(content_length_str) - 1);
    if (contlen_size >= 0) {
        size = (size_t) contlen_size + header_size;
        type = HTTP_CONTENT_LENGTH;
        goto do_ret;
    }

    /* GET requests (start with GET) or answers from the server (start
       with HTTP) */
    if (doesMatch("GET", 3, pkt->buffer, pkt->filled_size) ||
        doesMatch("HTTP", 4, pkt->buffer, pkt->filled_size)) {
        size = header_size;
        type = HTTP_HEADER_ONLY;
        goto do_ret;
    }

    /* No size was detectable yet header was found */
    type = HTTP_UNKNOWN;
    size = 0;

    do_ret:
    pkt->parent_message->claimed_size = size;
    pkt->parent_message->type = type;
    return type;
}

size_t packet_pending_bytes(struct http_packet_t *pkt) {
    struct http_message_t *msg = pkt->parent_message;

    /* Check Cache */
    if (pkt->expected_size > 0)
        goto pending_known;

    if (HTTP_UNSET == msg->type) {
        msg->type = packet_find_type(pkt);

        if (HTTP_CHUNKED == msg->type) {
            /* Note: this was the packet with the
           header of our chunked message. */

            /* Save any non-header data we got */
            ssize_t header_size = packet_get_header_size(pkt);

            /* Sanity check */
            if (header_size < 0 ||
                (size_t) header_size > pkt->filled_size)
                ERR_AND_EXIT("HTTP: Could not find header twice");

            NOTE("HTTP: Chunked header size is %ld bytes",
                 header_size);
            pkt->expected_size = (size_t) header_size;
            msg->claimed_size = 0;
            goto pending_known;
        }
    }

    if (HTTP_CHUNKED == msg->type) {
        if (pkt->filled_size == 0) {
            /* Grab chunk's mini-header */
            goto pending_known;
        }

        if (pkt->expected_size == 0) {
            /* Check chunk's mini-header */
            ssize_t size = packet_find_chunked_size(pkt);
            if (size <= 0) {
                ERR("=============================================");
                ERR("Malformed chunk-transport http header receivd");
                ERR("Missing chunk's mini-headers in first data");
                ERR("Have %d bytes", pkt->filled_size);
                printf("%.*s\n", (int) pkt->filled_size, pkt->buffer);
                ERR("Malformed chunk-transport http header receivd");
                ERR("=============================================");
                goto pending_known;
            }

            pkt->expected_size = (size_t) size;
            msg->claimed_size = 0;
        }

        goto pending_known;
    }
    if (HTTP_HEADER_ONLY == msg->type) {
        /* Note: we can only know it is header only
           when the buffer already contains the header.
           So this next call cannot fail. */
        pkt->expected_size = (size_t) packet_get_header_size(pkt);
        msg->claimed_size = pkt->expected_size;
        goto pending_known;
    }
    if (HTTP_CONTENT_LENGTH == msg->type) {
        /* Note: find_header() has
           filled msg's claimed_size */
        msg->claimed_size = msg->claimed_size;
        pkt->expected_size = msg->claimed_size;
        goto pending_known;
    }

    pending_known:

    /* Save excess data */
    if (pkt->expected_size && pkt->filled_size > pkt->expected_size)
        packet_store_excess(pkt);

    size_t expected = pkt->expected_size;
    if (expected == 0)
        expected = msg->claimed_size;
    if (expected == 0)
        expected = pkt->buffer_capacity;

    /* Sanity check */
    if (expected < pkt->filled_size)
        ERR_AND_EXIT("Expected cannot be larger than filled");

    size_t pending = expected - pkt->filled_size;

    /* Expand buffer as needed */
    while (pending + pkt->filled_size > pkt->buffer_capacity) {
        ssize_t size_added = packet_expand(pkt);
        if (size_added < 0) {
            WARN("packet at max allowed size");
            return 0;
        }
        if (size_added == 0) {
            ERR("Failed to expand packet");
            return 0;
        }
    }

    packet_check_completion(pkt);

    return pending;
}

void packet_mark_received(struct http_packet_t *pkt, size_t received) {
    struct http_message_t *msg = pkt->parent_message;
    msg->received_size += received;

    pkt->filled_size += received;
    if (received) {
        NOTE("HTTP: got %lu bytes so: pkt has %lu bytes, "
             "msg has %lu bytes",
             received, pkt->filled_size, msg->received_size);
    }

    packet_check_completion(pkt);

    if (pkt->filled_size > pkt->buffer_capacity)
        ERR_AND_EXIT("Overflowed packet's buffer");

    if (pkt->expected_size && pkt->filled_size > pkt->expected_size) {
        /* Store excess data */
        packet_store_excess(pkt);
    }
}

struct http_packet_t *packet_new(struct http_message_t *parent_msg) {
    struct http_packet_t *pkt = NULL;
    uint8_t *buf = NULL;
    size_t const capacity = BUFFER_STEP;

    assert(parent_msg != NULL);
    pkt = calloc(1, sizeof(*pkt));
    if (pkt == NULL) {
        ERR("failed to alloc packet");
        return NULL;
    }
    pkt->parent_message = parent_msg;
    pkt->expected_size = 0;

    /* Claim any spare data from prior packets */
    packet_take_spare(pkt);

    if (pkt->buffer == NULL) {
        buf = calloc(capacity, sizeof(*buf));
        if (buf == NULL) {
            ERR("failed to alloc space for packet's buffer or space for packet");
            free(pkt);
            return NULL;
        }

        /* Assemble packet */
        pkt->buffer = buf;
        pkt->buffer_capacity = capacity;
        pkt->filled_size = 0;
    }

    return pkt;
}

void packet_free(struct http_packet_t *pkt) {
    free(pkt->buffer);
    free(pkt);
}

#define MAX_PACKET_SIZE (1 << 26) /* 64MiB */

ssize_t packet_expand(struct http_packet_t *pkt) {
    size_t cur_size = pkt->buffer_capacity;
    size_t new_size = cur_size * 2;
    if (new_size > MAX_PACKET_SIZE) {
        WARN("HTTP: cannot expand packet beyond limit");
        return -1;
    }
    NOTE("HTTP: doubling packet buffer to %lu", new_size);

    uint8_t *new_buf = realloc(pkt->buffer, new_size);
    if (new_buf == NULL) {
        /* If realloc fails the original buffer is still valid */
        WARN("Failed to expand packet");
        return 0;
    }
    pkt->buffer = new_buf;
    pkt->buffer_capacity = new_size;

    size_t diff = new_size - cur_size;
    if (diff > SSIZE_MAX)
        ERR_AND_EXIT("Buffer expanded beyond sane limit");
    return (ssize_t) diff;
}
